/*
    Copyright (C) 2011 Rodrigo Castaño - Hugo Arregui, FuDePAN

    This file is part of the Prot-Filer Library

    Prot-Filer is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Prot-Filer is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Prot-Filer.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef CACHED_READER_H
#define CACHED_READER_H
#include <string>
#include "feca/feca.h"
#include "prot-filer/format_filer.h"

namespace prot_filer
{

using feca::FullCache;

template <class Reader, class T>
struct Constructor
{
    //TODO: returning a cointainer. But, this is a Feca design problem and should be resolved there before.
    static T build(size_t /*id*/, const Reader& /*reader*/)
    {
        return T();
    }
};

template <class Reader>
struct Constructor<Reader, AnglesData>
{
    static AnglesData build(size_t /*id*/, const Reader& reader)
    {
        return AnglesData(reader.get_atom_number() / ATOMS_PER_RESIDUE);
    }
};

template <class Reader>
struct Constructor<Reader, BasicProtein>
{
    static BasicProtein build(size_t /*id*/, const Reader& reader)
    {
        return BasicProtein(reader.get_atom_number() * ATOMS_PER_RESIDUE);
    }
};

template<int size>
struct SlidingWindowCache
{
    template<class Loader>
    struct CacheType
    {
        typedef typename feca::SlidingWindowCache<size, Loader>::Type Type;
    };
};

template<class Reader, class T>
class ReaderLoader
{
public:
    typedef ReaderLoader Loader;
    typedef size_t Key;
    typedef T CacheableObject;
    ReaderLoader(Reader& _reader) :
        reader(_reader),
        k(0)
    {};
    static Key next_key()
    {
        return 0;
    }
    static Key next_key(Key k)
    {
        return k + 1;
    }
    bool load_next(size_t& i, T& o)
    {
        i = k;
        ++k;
        return reader.read(o);
    }
    bool load(size_t i, T& o)
    {
        return reader.read(o, i);
    }
    Reader& get_reader()
    {
        return reader;
    }
    T createEmptyObject(size_t id)
    {
        return Constructor<Reader, T>::build(id, reader);
    }
private:
    Reader& reader;
    size_t k;
};

template<template<class> class Cache, class Reader, class T>
class CachedReader
{
public:
    typedef typename Cache<ReaderLoader<Reader, T> >::Type ReaderCache;
    CachedReader(Reader* reader_) :
        reader(reader_),
        cache(ReaderCache(new ReaderLoader<Reader, T>(*reader)))
    {};
    ~CachedReader()
    {
        delete reader;
    }
    bool open(const std::string& name)
    {
        return reader->open(name);
    }
    T* read(size_t n)
    {
        return cache.get(n);
    }
    void close()
    {
        reader->close();
    }
    const Reader& get_reader() const
    {
        return *reader;
    }

private:
    Reader* const reader;
    ReaderCache cache;
};

class FragmentsFromReader : public Fragments
{
    typedef CachedReader<FullCache, AnglesReader, AnglesData> FCachedReader;
public:
    FragmentsFromReader(AnglesReader* reader) :
        reader(FCachedReader(reader))
    {}
    virtual unsigned int get_fragment_size() const
    {
        return reader.get_reader().get_atom_number() / ATOMS_PER_RESIDUE;
    }
    virtual AnglesData& operator[](unsigned int i)
    {
        return (*reader.read(i));
    }
private:
    FCachedReader reader;
};
}
#endif
